/*
 * AnnotationScanner.java July 2009
 *
 * Copyright (C) 2009, Quirino Zagarese <quirino@laszloitalia.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA  02111-1307  USA
 */
package org.eu4ria.core;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.ParameterAnnotationsAttribute;
import javassist.bytecode.annotation.Annotation;

public class AnnotationScanner {

	private static ClassPool classPool = ClassPool.getDefault();

	private AnnotationScanner(){
		ClassPool.releaseUnmodifiedClassFile = true;
	}

	public static void addPath(File file) throws NotFoundException {
		classPool.appendPathList(file.getAbsolutePath());
	}

	public static boolean isAnnotationPresent(Method method, Class annotationClass){
		CtMethod ctMethod = findMethod(method);
		AnnotationsAttribute attr = (AnnotationsAttribute)
        ctMethod.getMethodInfo().getAttribute(AnnotationsAttribute.visibleTag);
		if(attr == null){
			return false;
		}
		Annotation annotation = attr.getAnnotation(annotationClass.getName());
		return (annotation!=null);
	}

	public static Annotation[][] getParametersAnnotations(Method method){
		CtMethod ctMethod = findMethod(method);
		MethodInfo methodInfo = ctMethod.getMethodInfo();
		ParameterAnnotationsAttribute paramsVisible = (ParameterAnnotationsAttribute) methodInfo.getAttribute(ParameterAnnotationsAttribute.visibleTag);
		if(paramsVisible != null){
			return paramsVisible.getAnnotations();
		}
		int size = 0;
		try {
			size = ctMethod.getParameterTypes().length;
		} catch (NotFoundException e) {
			e.printStackTrace();
		}
		return new Annotation[size][0];
	}

	public static Object[] getAnnotationList(Class clazz){
		try {
			return classPool.get(clazz.getName()).getAnnotations();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (NotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static CtClass getCtClass(String name){
		try {
			return classPool.get(name);
		} catch (NotFoundException e) {
			e.printStackTrace();
			return null;
		}
	}

	public static Annotation[] getDeclaredAnnotations(Field field) {
		CtClass ctClass = null;
		CtField ctField = null;
		try {
			ctClass = classPool.get(field.getDeclaringClass().getName());
			ctField = ctClass.getDeclaredField(field.getName());
		} catch (NotFoundException e) {
			e.printStackTrace();
		}
		if (ctField != null) {
			FieldInfo info = ctField.getFieldInfo();
			AnnotationsAttribute attr = (AnnotationsAttribute) info
					.getAttribute(AnnotationsAttribute.visibleTag);
			if (attr != null) {
				return attr.getAnnotations();
			}
		}
		return new Annotation[0];
	}

	public static Annotation[] getDeclaredAnnotations(Method method) {
		CtMethod ctMethod = null;
		ctMethod = findMethod(method);

		if (ctMethod != null) {
			MethodInfo info = ctMethod.getMethodInfo();
			AnnotationsAttribute attr = (AnnotationsAttribute) info
					.getAttribute(AnnotationsAttribute.visibleTag);
			if (attr != null) {
				return attr.getAnnotations();
			}
		}
		return new Annotation[0];
	}

	public static boolean isAnnotationPresent(Class<?> type, Class<?> name) {
		ClassFile cf = null;
		if(type.isArray()){
			type=type.getComponentType();
		}
		try {
			cf = classPool.get(type.getName()).getClassFile();
		} catch (NotFoundException e) {
			e.printStackTrace();
		}
		AnnotationsAttribute attr = (AnnotationsAttribute) cf
				.getAttribute(AnnotationsAttribute.visibleTag);
		if (attr == null) {
			return false;
		}
		Annotation annotation = attr.getAnnotation(name.getName());
		return (annotation != null);
	}

	public static Annotation getAnnotation(Class<?> type, Class<?> name) {
		ClassFile cf = null;
		if(type.isArray()){
			type=type.getComponentType();
		}
		try {
			cf = classPool.get(type.getName()).getClassFile();
		} catch (NotFoundException e) {
			e.printStackTrace();
		}
		AnnotationsAttribute attr = (AnnotationsAttribute) cf
				.getAttribute(AnnotationsAttribute.visibleTag);
		if (attr != null) {
			return attr.getAnnotation(name.getName());
		}
		return null;
	}

	public static Annotation getAnnotation(Method method, Class<?> name) {
		CtMethod ctMethod = null;
		ctMethod = findMethod(method);

		if (ctMethod != null) {
			MethodInfo info = ctMethod.getMethodInfo();
			AnnotationsAttribute attr = (AnnotationsAttribute) info
					.getAttribute(AnnotationsAttribute.visibleTag);
			if (attr != null) {
				return attr.getAnnotation(name.getName());
			}
		}
		return null;
	}

	private static CtMethod findMethod(Method method) {
		try {
			CtClass ctClass = classPool.get(method.getDeclaringClass().getName());
			Class[] paramTypes = method.getParameterTypes();
			CtClass[] ctParams = new CtClass[paramTypes.length];
			for (int i = 0; i < paramTypes.length; i++) {
				ctParams[i] = classPool.get(paramTypes[i].getName());
			}
			return ctClass.getDeclaredMethod(method.getName(), ctParams);
		} catch (NotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}


}
